package com.sqtracker.recommender.algorithm;

import com.sqtracker.recommender.dto.RecommendationRequest;
import com.sqtracker.recommender.dto.RecommendationResponse;
import com.sqtracker.recommender.model.TorrentMetadataExt;
import com.sqtracker.recommender.model.UserBehavior;
import com.sqtracker.recommender.repository.TorrentMetadataExtRepository;
import com.sqtracker.recommender.repository.UserBehaviorRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 探索性推荐算法实现类
 *
 * 该算法旨在帮助用户发现新的、多样化的内容，避免推荐系统的"信息茧房"效应。
 * 通过推荐新上传的内容、热门内容和多样化的随机内容，拓展用户的兴趣范围。
 *
 * 主要特点：
 * 1. 新内容推荐：推荐最新上传的内容（3天内）
 * 2. 热门内容推荐：推荐当前最受欢迎的内容
 * 3. 多样化推荐：通过语言分类等维度确保推荐内容的多样性
 * 4. 避免重复：不推荐用户已交互过的内容
 * 5. 趋势推荐：支持按分类的趋势内容推荐
 *
 * 推荐比例分配：
 * - 新内容：30%
 * - 热门内容：30%
 * - 多样化随机：40%
 *
 * @author sqtracker
 * @version 1.0
 */
@Component
public class ExplorationRecommender implements RecommendationAlgorithm {

    private static final Logger logger = LoggerFactory.getLogger(ExplorationRecommender.class);

    private final TorrentMetadataExtRepository torrentMetadataExtRepository;
    private final UserBehaviorRepository userBehaviorRepository;

    /**
     * 随机数生成器，用于多样化推荐
     */
    private Random random = new Random();

    /**
     * 构造函数，通过依赖注入初始化仓库
     */
    @Autowired
    public ExplorationRecommender(
            TorrentMetadataExtRepository torrentMetadataExtRepository,
            UserBehaviorRepository userBehaviorRepository) {
        this.torrentMetadataExtRepository = torrentMetadataExtRepository;
        this.userBehaviorRepository = userBehaviorRepository;
    }

    /**
     * 探索性推荐主方法
     *
     * 算法流程：
     * 1. 获取用户已交互的内容列表（避免重复推荐）
     * 2. 按照固定比例分配推荐类型（新内容30%、热门30%、多样化40%）
     * 3. 分别获取各类型的推荐内容
     * 4. 合并结果并随机打乱顺序
     * 5. 返回最终推荐列表
     *
     * @param request 推荐请求，包含用户ID和推荐数量
     * @return 推荐响应，包含多样化的推荐内容列表
     */
    @Override
    public RecommendationResponse recommend(RecommendationRequest request) {
        String userId = request.getUserId();
        int count = request.getCount();
        logger.debug("Exploration recommending for userId: {}, count: {}", userId, count);

        // 步骤1：获取用户已交互的内容ID集合，避免重复推荐
        Set<String> interactedTorrentIds = Collections.emptySet();
        if (userId != null) {
            interactedTorrentIds = userBehaviorRepository.findByUserId(userId)
                    .stream()
                    .map(UserBehavior::getTorrentId)
                    .collect(Collectors.toSet());
            logger.debug("User {} interacted with {} items, will be excluded from exploration.", userId, interactedTorrentIds.size());
        }

        // 步骤2：按比例分配各类型推荐数量
        int newCount = (int) Math.max(0, count * 0.3);        // 新内容：30%
        int popularCount = (int) Math.max(0, count * 0.3);    // 热门内容：30%
        int randomCount = Math.max(0, count - newCount - popularCount); // 多样化随机：剩余40%

        List<Map<String, Object>> recommendations = new ArrayList<>();
        Set<String> recommendedInThisBatch = new HashSet<>(); // 防止本次推荐中出现重复

        // 步骤3：获取新内容推荐（最近3天上传的内容）
        if (newCount > 0) {
            Date threeDaysAgo = Date.from(Instant.now().minus(3, ChronoUnit.DAYS));
            logger.debug("Fetching new torrents (last 3 days) for exploration. Target count: {}", newCount);

            // 按最后更新时间降序获取新内容
            List<TorrentMetadataExt> newTorrents = torrentMetadataExtRepository.findByLastUpdatedGreaterThan(
                    threeDaysAgo,
                    Sort.by(Sort.Direction.DESC, "lastUpdated")
            );

            final Set<String> finalInteractedTorrentIds = interactedTorrentIds;
            // 过滤掉用户已交互的内容和本次已推荐的内容
            List<TorrentMetadataExt> filteredNewTorrents = newTorrents.stream()
                    .filter(t -> !finalInteractedTorrentIds.contains(t.getTorrentId()) && recommendedInThisBatch.add(t.getTorrentId()))
                    .collect(Collectors.toList());

            logger.debug("Found {} new torrents, {} after filtering interactions and batch duplicates.", newTorrents.size(), filteredNewTorrents.size());

            recommendations.addAll(buildRecommendationsFromTorrents(
                    getRandomSubset(filteredNewTorrents, newCount),
                    "exploration-new",
                    "新上传的热门资源"
            ));
        }

        // 步骤4：获取热门内容推荐
        if (popularCount > 0) {
            logger.debug("Fetching popular torrents for exploration. Target count: {}", popularCount);

            // 按热门度降序获取内容
            List<TorrentMetadataExt> popularTorrents = torrentMetadataExtRepository.findAll(
                    Sort.by(Sort.Direction.DESC, "popularity")
            );

            final Set<String> finalInteractedTorrentIds1 = interactedTorrentIds;
            // 过滤掉用户已交互的内容和本次已推荐的内容
            List<TorrentMetadataExt> filteredPopularTorrents = popularTorrents.stream()
                    .filter(t -> !finalInteractedTorrentIds1.contains(t.getTorrentId()) && recommendedInThisBatch.add(t.getTorrentId()))
                    .collect(Collectors.toList());

            logger.debug("Found {} popular torrents, {} after filtering interactions and batch duplicates.", popularTorrents.size(), filteredPopularTorrents.size());

            recommendations.addAll(buildRecommendationsFromTorrents(
                    getRandomSubset(filteredPopularTorrents, popularCount),
                    "exploration-popular",
                    "热门下载资源"
            ));
        }

        // 步骤5：获取多样化随机推荐
        if (randomCount > 0) {
            logger.debug("Fetching diverse random torrents for exploration. Target count: {}", randomCount);

            List<TorrentMetadataExt> allTorrents = torrentMetadataExtRepository.findAll();
            final Set<String> finalInteractedTorrentIds2 = interactedTorrentIds;

            // 过滤掉用户已交互的内容和本次已推荐的内容
            List<TorrentMetadataExt> filteredRandomTorrents = allTorrents.stream()
                    .filter(t -> !finalInteractedTorrentIds2.contains(t.getTorrentId()) && recommendedInThisBatch.add(t.getTorrentId()))
                    .collect(Collectors.toList());

            logger.debug("Found {} total torrents for random pool, {} after filtering interactions and batch duplicates.", allTorrents.size(), filteredRandomTorrents.size());

            // 通过语言分类实现多样化推荐
            Map<String, List<TorrentMetadataExt>> torrentsByLanguage = filteredRandomTorrents.stream()
                    .filter(t -> t.getLanguage() != null)
                    .collect(Collectors.groupingBy(TorrentMetadataExt::getLanguage));

            logger.debug("Grouped random torrents by language: {} languages found.", torrentsByLanguage.size());

            List<TorrentMetadataExt> diverseTorrents = new ArrayList<>();
            List<String> languageKeys = new ArrayList<>(torrentsByLanguage.keySet());
            Collections.shuffle(languageKeys, random); // 随机打乱语言顺序

            // 从每种语言中选择一个内容，确保多样性
            for (String langKey : languageKeys) {
                if (diverseTorrents.size() < randomCount) {
                    List<TorrentMetadataExt> langTorrents = torrentsByLanguage.get(langKey);
                    if (!langTorrents.isEmpty()) {
                        diverseTorrents.add(langTorrents.get(random.nextInt(langTorrents.size())));
                    }
                } else {
                    break;
                }
            }
            logger.debug("Selected {} diverse torrents by language strategy.", diverseTorrents.size());

            // 如果按语言分类还不够数量，从剩余内容中随机选择
            if (diverseTorrents.size() < randomCount) {
                int remainingNeeded = randomCount - diverseTorrents.size();
                Set<String> alreadySelectedDiverseIds = diverseTorrents.stream()
                        .map(TorrentMetadataExt::getTorrentId)
                        .collect(Collectors.toSet());

                List<TorrentMetadataExt> remainingRandomPool = filteredRandomTorrents.stream()
                        .filter(t -> !alreadySelectedDiverseIds.contains(t.getTorrentId()))
                        .collect(Collectors.toList());

                diverseTorrents.addAll(getRandomSubset(remainingRandomPool, remainingNeeded));
                logger.debug("Added {} more random torrents to meet diverse count. Total diverse: {}.", remainingNeeded, diverseTorrents.size());
            }

            recommendations.addAll(buildRecommendationsFromTorrents(
                    getRandomSubset(diverseTorrents, randomCount),
                    "exploration-diverse",
                    "探索发现"
            ));
        }

        // 步骤6：随机打乱推荐结果，避免固定的推荐模式
        Collections.shuffle(recommendations);
        logger.info("Exploration generated {} recommendations for userId: {} before final count limit.", recommendations.size(), userId);

        // 步骤7：构建最终响应
        RecommendationResponse response = new RecommendationResponse();
        if (recommendations.size() > count) {
            response.setRecommendations(new ArrayList<>(recommendations.subList(0, count)));
        } else {
            response.setRecommendations(recommendations);
        }
        logger.info("Exploration final recommendations count for userId {}: {}", userId, response.getRecommendations().size());

        // 警告：如果推荐结果为空
        if (response.getRecommendations().isEmpty() && count > 0) {
            logger.warn("Exploration recommender for user {} (target count {}) resulted in an empty list.", userId, count);
        }
        return response;
    }

    /**
     * 推荐趋势内容
     *
     * 根据热门度和最后更新时间推荐当前的趋势内容
     * 支持按分类过滤
     *
     * @param request 推荐请求，可包含分类ID和推荐数量
     * @return 推荐响应，包含趋势内容列表
     */
    public RecommendationResponse recommendTrending(RecommendationRequest request) {
        int count = request.getCount();
        String categoryId = request.getCategoryId();
        logger.debug("Recommending trending items. Count: {}, CategoryId: {}", count, categoryId);

        List<TorrentMetadataExt> trendingTorrents;

        // 如果指定了分类，则只推荐该分类的趋势内容
        if (categoryId != null && !categoryId.isEmpty()) {
            logger.debug("Fetching trending for category: {}", categoryId);
            trendingTorrents = torrentMetadataExtRepository.findByTorrentCategoryId(
                    categoryId,
                    Sort.by(Sort.Direction.DESC, "popularity", "lastUpdated")
            );
        } else {
            // 否则推荐所有分类的趋势内容
            logger.debug("Fetching overall trending items.");
            trendingTorrents = torrentMetadataExtRepository.findAll(
                    Sort.by(Sort.Direction.DESC, "popularity", "lastUpdated")
            );
        }
        logger.debug("Found {} raw trending torrents.", trendingTorrents.size());

        // 限制数量
        List<TorrentMetadataExt> selectedTorrents = trendingTorrents.stream()
                .limit(count)
                .collect(Collectors.toList());

        List<Map<String, Object>> recommendations = buildRecommendationsFromTorrents(
                selectedTorrents,
                "trending",
                "热门资源"
        );
        logger.info("Generated {} trending recommendations.", recommendations.size());

        RecommendationResponse response = new RecommendationResponse();
        response.setRecommendations(recommendations);
        return response;
    }

    /**
     * 从列表中随机选择指定数量的子集
     *
     * @param list 原始列表
     * @param size 需要选择的数量
     * @param <T> 列表元素类型
     * @return 随机选择的子集
     */
    private <T> List<T> getRandomSubset(List<T> list, int size) {
        if (list == null || list.isEmpty() || size <= 0) {
            return Collections.emptyList();
        }
        if (list.size() <= size) {
            return new ArrayList<>(list);
        }

        // 创建原列表的副本并打乱顺序
        List<T> originalCopy = new ArrayList<>(list);
        Collections.shuffle(originalCopy, random);
        return new ArrayList<>(originalCopy.subList(0, size));
    }

    /**
     * 从torrent列表构建推荐条目列表
     *
     * @param torrents 要推荐的torrent列表
     * @param algorithm 算法名称
     * @param reason 推荐理由
     * @return 推荐条目列表
     */
    private List<Map<String, Object>> buildRecommendationsFromTorrents(
            List<TorrentMetadataExt> torrents,
            String algorithm,
            String reason) {

        List<Map<String, Object>> recommendations = new ArrayList<>();
        if (torrents == null) {
            return recommendations;
        }

        for (TorrentMetadataExt torrent : torrents) {
            Map<String, Object> item = new HashMap<>();
            item.put("torrentId", torrent.getTorrentId());
            item.put("score", calculateScore(torrent));  // 计算综合评分
            item.put("algorithm", algorithm);
            item.put("reason", reason);
            recommendations.add(item);
        }
        return recommendations;
    }

    /**
     * 计算torrent的综合评分
     *
     * 基于内容的新鲜度、热门度和发布者信誉度计算综合评分
     *
     * 评分权重分配：
     * - 新鲜度：40%
     * - 热门度：40%
     * - 发布者信誉：20%
     *
     * @param torrent torrent元数据
     * @return 综合评分
     */
    private double calculateScore(TorrentMetadataExt torrent) {
        double score = 0.0;

        // 新鲜度权重：40%
        if (torrent.getFreshness() != null) {
            score += torrent.getFreshness() * 0.4;
        }

        // 热门度权重：40%
        if (torrent.getPopularity() != null) {
            score += torrent.getPopularity() * 0.4;
        }

        // 发布者信誉权重：20%
        if (torrent.getPublisherReputation() != null) {
            score += torrent.getPublisherReputation() * 0.2;
        }

        return score;
    }
}